﻿using System;
using System.Linq;
using System.ServiceModel;
using System.Threading;
using System.Collections.Generic;
using System.Diagnostics;

namespace MultiViewer.Network 
{
    [ServiceBehavior(InstanceContextMode=InstanceContextMode.Single)]
    public class ServerNode : Node, IServerNode
    {
        private IClientNode[] clients;

        private ServiceHost host;

        public ServerNode(params string[] args)
        {
            string ncs = GetParameter("multiViewerNodeCount","mvnodes",args);
            if (!Int32.TryParse(ncs, out nodeCount))
                throw new Exception("Node count is not specified neither in config nor in command line args");
            if (nodeCount < 1)
                throw new Exception("Node count must be positive");
            clients = new IClientNode[nodeCount - 1];
            host = new ServiceHost(this);
            host.Open();
            inited = true;
        }

        public override int[] ConnectedNodes
        {
            get
            {
                lock (clients)
                {
                    List<int> result = new List<int>();
                    result.Add(0); // Server (self) is surely connected
                    for (int i = 0; i < clients.Length; i++)
                        if (clients[i] != null)
                            result.Add(i + 1);
                    return result.ToArray();
                }
            }
        }

        public int Connect(int nodeId)
        {
            if (nodeId < 1 || nodeId >= nodeCount)
                throw new Exception("Node id is out of range");
            if (clients[nodeId - 1] != null)
                throw new Exception("Node id is already used");
            lock (clients)
            {
                clients[nodeId - 1] = OperationContext.Current.GetCallbackChannel<IClientNode>();
                OnNodeConnected(nodeId);
                return nodeCount;
            }
        }

        public void Disconnect(int nodeId)
        {
            if (nodeId < 1 || nodeId >= nodeCount)
                throw new Exception("Node id is out of range");
            if (clients[nodeId - 1] == null)
                throw new Exception("Node id is already used");
            lock (clients)
            {
                clients[nodeId - 1] = null;
                OnNodeDisconnected(nodeId);
            }
        } 

        public override Response[] Send(int[] nodes, Message message)
        {            
            Response[] results = new Response[nodes.Length];
            AutoResetEvent[] completed = new AutoResetEvent[nodes.Length];
            for (int i = 0; i < nodes.Length; i++)
            {
                completed[i] = new AutoResetEvent(false);
                ThreadPool.QueueUserWorkItem(obj =>
                {
                    int idx = (int)obj;
                    results[idx] = Send(nodes[idx], message);
                    completed[idx].Set();
                }, i);
            }
            WaitHandle.WaitAll(completed);
            return results;
        }

        private Response Send(int node, Message request)
        {
            if (node > 0 && clients[node - 1] == null)
                return new Response(new Exception(String.Format("Node {0} is not connected", node)));
            else
                try
                {
                    object result = 0;
                    if (node == 0)
                        result = HandleMessage(request);
                    else
                        result = clients[node-1].OnMessage(request);
                    return new Response(result);
                }
                catch (Exception exc)
                {
                    return new Response(exc);
                }
        }

        public override void Notify(int[] nodes, Message request)
        {
            IClientNode[] clients = (IClientNode[])this.clients.Clone();
            foreach (int node in nodes)
                ThreadPool.QueueUserWorkItem(obj =>
                {
                    int n = (int)obj;
                    try
                    {
                        if (n == 0)
                            HandleNotification(request);
                        else if(clients[n - 1] != null)
                            clients[n - 1].OnNotification(request);
                    }
                    catch (Exception exc)
                    {
                        Trace.WriteLine("Exception in Notify: " + exc.Message);
                    }
                }, node);
        }
    }
}